package com.hjx.http.util;

import com.hjx.http.conn.ProxyBindRoutePlanner;
import com.hjx.http.cookie.ModifiableCookieStore;
import com.hjx.http.impl.client.MyRedirectStrategy;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.ObjectMapper;
import com.mashape.unirest.http.Unirest;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 通用请求工具类
 *
 * @author houjinxin
 * @date 2016/12/31
 */
public class RequestUtil {

    private static Logger logger = LoggerFactory.getLogger(RequestUtil.class);

    private static final int DEFAULT_SO_TIMEOUT = 20 * 1000;
    private static final int DEFAULT_CONNECT_TIMEOUT = 20 * 1000;

    private static final ModifiableCookieStore cookieStore = new ModifiableCookieStore();

    static {
        try {
            Unirest.setObjectMapper(new ObjectMapper() {
                private org.codehaus.jackson.map.ObjectMapper jacksonObjectMapper  = new org.codehaus.jackson.map.ObjectMapper();

                @Override
                public <T> T readValue(String value, Class<T> valueType) {
                    try {
                        return jacksonObjectMapper.readValue(value, valueType);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }

                @Override
                public String writeValue(Object value) {
                    try {
                        return jacksonObjectMapper.writeValueAsString(value);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            Unirest.setHttpClient(initHttpClient(true));
//            Unirest.setAsyncHttpClient(initHttpClient(true));
        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化httpsclient
     *
     * @return httpClient 对象
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */

    public static CloseableHttpClient initHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        return initHttpClient(true);
    }

    public static CloseableHttpClient initHttpClient(boolean secure) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {

        RequestConfig defaultRequestConfig = RequestConfig.custom()
                .setSocketTimeout(DEFAULT_CONNECT_TIMEOUT)
                .setConnectTimeout(DEFAULT_SO_TIMEOUT)
                .setCookieSpec(CookieSpecs.DEFAULT) //此处使用默认的Cookie规范，可以让后续请求共享Cookie
//                .setCookieSpec(CookieSpecs.BEST_MATCH)
//                .setCookieSpec(CookieSpecs.NETSCAPE)
//                .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)

//                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
//                .setCookieSpec(CookieSpecs.STANDARD)
//                .setCookieSpec(CookieSpecs.STANDARD_STRICT)
                .setRedirectsEnabled(true)
                .setCircularRedirectsAllowed(false)
//                .setMaxRedirects(20)
                .build();

//        HttpAsyncClientBuilder builder = HttpAsyncClients.custom()
//                .setDefaultRequestConfig(defaultRequestConfig)
//                .setDefaultCookieStore(cookieStore)
//                .setRedirectStrategy(new LaxRedirectStrategy())
//                .setMaxConnTotal(1000)
//                .setMaxConnPerRoute(200)
////                .evictExpiredConnections()
////                .evictIdleConnections(5L, TimeUnit.SECONDS)
//                .setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE)
//                .setRoutePlanner(new ProxyBindRoutePlanner());
//                ;
        HttpClientBuilder builder = HttpClients.custom()
                .setDefaultRequestConfig(defaultRequestConfig)
                .setDefaultCookieStore(cookieStore)
                .setRedirectStrategy(new MyRedirectStrategy())
                .setMaxConnTotal(1000)
                .setMaxConnPerRoute(200)
//                .evictExpiredConnections()
//                .evictIdleConnections(5L, TimeUnit.SECONDS)
                .setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE)
                .setRoutePlanner(new ProxyBindRoutePlanner());

        if (secure) {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            builder.setSSLContext(sslContext);
//                    builder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext));
        }

        return builder.build();
    }

    public static ModifiableCookieStore getModifiableCookieStore() {
        return cookieStore;
    }

    public static void addCookie(String name, String value) {
        getModifiableCookieStore().addCookie(new BasicClientCookie(name, value));
    }

    public static void addCookie(String name, String value, String domain, Date expiry, String path) {
        getModifiableCookieStore().addCookie(new BasicClientCookie(name, value){{
            setDomain(domain);
            setExpiryDate(expiry);
            setPath(path);
        }});
    }

    public static void removeCookie(String name) {
        getModifiableCookieStore().remove(name);
    }

    public static void clearCookies() {
        getModifiableCookieStore().clear();
    }

    /**
     * GET 方法响应转为字符串
     * @param url 地址
     * @return 网页文本
     * @throws Exception
     */
    public static String get(String url) throws Exception {
        return get(url, null);
    }

    public static String get(String url, final Map<String, String> params) throws Exception {
        return get(url, params, null);
    }

    public static String get(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        Map<String, Object> queryStringMap = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

//        long start = System.nanoTime();
        HttpResponse<String> response = Unirest.get(url)
            .headers(headers)
            .queryString(queryStringMap)
            .asString();
//        long end = System.nanoTime();
//        logger.info("url {} 请求时间{}s",url, (double)((end - start)/1000000));
        return printResponse(response, url);
    }

    public static String getNoProxy(String url, final Map<String, String> params) throws Exception {
        return getNoProxy(url, params, null);
    }

    public static String getNoProxy(String url) throws Exception {
        return getNoProxy(url, null);
    }

    public static String getNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        String response = get(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return response;
    }

    /**
     *  GET 方法响应转为流
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static InputStream getAsStream(String url) throws Exception {
        return getAsStream(url, null, null);
    }

    public static InputStream getAsStream(String url, final Map<String, String> params) throws Exception {
        return getAsStream(url, params, null);
    }

    public static InputStream getAsStream(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        Map<String, Object> queryStringMap = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

        HttpResponse<InputStream> response = Unirest.get(url)
            .headers(headers)
            .queryString(queryStringMap)
            .asBinary();
        printStreamResponse(response, url);
        return response.getBody();
    }

    /**
     *  GET 方法响应转为流 无代理
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static InputStream getAsStreamNoProxy(String url) throws Exception {
        return getAsStreamNoProxy(url, null, null);
    }

    public static InputStream getAsStreamNoProxy(String url, final Map<String, String> params) throws Exception {
        return getAsStreamNoProxy(url, params, null);
    }

    public static InputStream getAsStreamNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        InputStream inputStream = getAsStream(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return inputStream;
    }

    /**
     *  GET 方法响应转为字节数组
     * @param url
     * @return
     * @throws Exception
     */
    public static byte[] getAsBytes(String url) throws Exception {
        return getAsBytes(url, null, null);
    }

    public static byte[] getAsBytes(String url, final Map<String, String> params) throws Exception {
        return getAsBytes(url, params, null);
    }

    public static byte[] getAsBytes(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        InputStream inputStream = getAsStream(url, params, headers);
        return IOUtils.toByteArray(inputStream);
    }

    /**
     *  GET 方法响应转为字节数组 无代理
     * @param url
     * @return
     * @throws Exception
     */
    public static byte[] getAsBytesNoProxy(String url) throws Exception {
        return getAsBytesNoProxy(url, null, null);
    }

    public static byte[] getAsBytesNoProxy(String url, final Map<String, String> params) throws Exception {
        return getAsBytesNoProxy(url, params, null);
    }

    public static byte[] getAsBytesNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        byte[] bytes = getAsBytes(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return bytes;
    }

    public static File getAsFile(String fileFullName, String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        File file = new File(fileFullName);
        FileUtils.writeByteArrayToFile(file, IOUtils.toByteArray(getAsStream(url, params, headers)));
        return file;
    }

    public static File getAsFileNoProxy(String fileFullName, String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        File file = getAsFile(fileFullName, url, params, headers);
        ProxyFetchUtil.enableProxy();
        return file;
    }

    /**
     *  GET 方法响应转为InputStreamEntity
     * @param url
     * @return
     * @throws Exception
     */
    public static InputStreamEntity getAsInputStreamEntity(String url) throws Exception {
        return getAsInputStreamEntity(url, null, null);
    }

    public static InputStreamEntity getAsInputStreamEntity(String url, final Map<String, String> params) throws Exception {
        return getAsInputStreamEntity(url, params, null);
    }

    public static InputStreamEntity getAsInputStreamEntity(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        Map<String, Object> queryStringMap = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

        HttpResponse<InputStream> response = Unirest.get(url)
                .headers(headers)
                .queryString(queryStringMap)
                .asBinary();
        InputStreamEntity entity = new InputStreamEntity(response.getBody());
        entity.setContentType(response.getHeaders().get("Content-Type").get(0));
        return entity;
    }

    public static InputStreamEntity getAsInputStreamEntityNoProxy(String url) throws Exception {
        return getAsInputStreamEntityNoProxy(url, null);
    }

    public static InputStreamEntity getAsInputStreamEntityNoProxy(String url, final Map<String, String> params) throws Exception {
        return getAsInputStreamEntityNoProxy(url, params, null);
    }

    public static InputStreamEntity getAsInputStreamEntityNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        InputStreamEntity entity = getAsInputStreamEntity(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return entity;
    }

    /**
     * POST 响应为InputStream, 带Header与不带Header的方法
     *
     * @param url
     * @param params
     * @return
     * @throws Exception
     */
    public static InputStream postAsStream(String url, final Map<String, String> params) throws Exception {
        return postAsStream(url, params, null);
    }

    public static InputStream postAsStream(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        Map<String, Object> fields = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

        HttpResponse<InputStream> response = Unirest.post(url)
                .headers(headers)
                .fields(fields)
                .asBinary();
        printStreamResponse(response, url);
        return response.getBody();
    }

    public static InputStream postAsStreamNoProxy(String url, final Map<String, String> params) throws Exception {
        return postAsStreamNoProxy(url, params, null);
    }

    public static InputStream postAsStreamNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        InputStream inputStream = postAsStream(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return inputStream;
    }

    public static byte[] postAsBytes(String url, final Map<String, String> params) throws Exception {
        return IOUtils.toByteArray(postAsStream(url, params, null));
    }

    public static byte[] postAsBytes(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        return IOUtils.toByteArray(postAsStream(url, params, headers));
    }

    public static byte[] postAsBytesNoProxy(String url, final Map<String, String> params) throws Exception {
        return postAsBytesNoProxy(url, params, null);
    }

    public static byte[] postAsBytesNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        byte[] bytes = postAsBytes(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return bytes;
    }

    public static File postAsFile(String fileFullName, String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        printRequest(url, params, headers);
        File file = new File(fileFullName);
        FileUtils.writeByteArrayToFile(file, IOUtils.toByteArray(postAsStream(url, params, headers)));
        return file;
    }


    public static File postAsFileNoProxy(String fileFullName, String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        File file = postAsFile(fileFullName, url, params, headers);
        ProxyFetchUtil.enableProxy();
        return file;
    }

    public static String post(String url, final Map<String, String> params) throws Exception {
        return post(url, params, null);
    }

    public static String post(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {

        printRequest(url, params, headers);
        Map<String, Object> fields = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

        HttpResponse<String> response = Unirest.post(url)
                .headers(headers)
                .fields(fields)
                .asString();
        return printResponse(response, url);
    }

    public static String postNoProxy(String url, final Map<String, String> params) throws Exception {
        return postNoProxy(url, params, null);
    }

    public static String postNoProxy(String url, final Map<String, String> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        String response = post(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return response;
    }


    public static String postJson(String url, final Map<String, Object> params, Map<String, String> headers) throws Exception {

        printRequest(url, params, headers);
        Map<String, Object> fields = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

        HttpResponse<String> response = Unirest.post(url)
            .headers(headers)
            .body(fields)
            .asString();
        return printResponse(response, url);
    }

    public static String postJsonNoProxy(String url, final Map<String, Object> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        String response = postJson(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return response;
    }


    public static JsonNode postAsJson(String url, final Map<String, Object> params, Map<String, String> headers) throws Exception {

        printRequest(url, params, headers);
        Map<String, Object> fields = params != null ? new LinkedHashMap<String, Object>() {{
            putAll(params);
        }} : null;

        HttpResponse<JsonNode> response = Unirest.post(url)
            .headers(headers)
            .body(fields)
            .asJson();
        return response.getBody();
    }

    public static JsonNode postAsJsonNoProxy(String url, final Map<String, Object> params, Map<String, String> headers) throws Exception {
        ProxyFetchUtil.disableProxy();
        JsonNode jsonNode = postAsJson(url, params, headers);
        ProxyFetchUtil.enableProxy();
        return jsonNode;
    }

    private static void printHeaders(Map<String, String> headers) {
        logger.info("               Headers 总数：{}，详情如下：", headers.size());
        logger.info("=================================Header=====================================");
        for (Map.Entry<String, String> entry: headers.entrySet()) {
            logger.info("{} : {}", entry.getKey(), entry.getValue());
        }
        logger.info("=================================Header=====================================");
    }

    private static void printCookies(){

        CookieStore cookieStore = RequestUtil.getModifiableCookieStore();
        List<Cookie> cookies = cookieStore.getCookies();
        logger.info("               Cookies 总数：{}，详情如下：", cookies.size());
        logger.info("=================================Cookies=====================================");
        for (int i = 0; i < cookies.size(); i++) {
            logger.info("{}", cookies.get(i));
        }
        logger.info("=================================Cookies=====================================");
    }

    private static void printRequestParameters(Map<String, Object> requestParameters) {
        logger.info("               RequestParameters 总数：{}，详情如下：", requestParameters.size());
        logger.info("=================================RequestParameters=====================================");
        for (Map.Entry<String, Object> entry: requestParameters.entrySet()) {
            logger.info("{}={}", entry.getKey(), entry.getValue());
        }
        logger.info("=================================RequestParameters=====================================");
    }


    private static void printUrl(String url) {
        logger.info("               Url 详情：");
        logger.info("=================================URL=====================================");
        logger.info("URL => {}", url);
        logger.info("=================================URL=====================================");
    }

    private static void printStreamResponse(HttpResponse<InputStream> response, String url) {
        logger.info("               Response 详情：");
        logger.info("=================================Response=====================================");
        logger.info("Thread 【 {} 】Proxy 【 {} 】 Response [ statusText => {}, status = {} ]",
                Thread.currentThread().getName(),
                ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
                response.getStatusText(),
                response.getStatus());
        if (response.getStatus() == 403) {
//            collect403(url, BASE_PATH);
            logger.info("Thread 【 {} 】Proxy 【 {} 】 【 {} 】request【 {} 】is rejected by server! response ==> \n {}",
                    Thread.currentThread().getName(),
                    ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
                    response.getStatus(),
                    url,
                    response.getBody());
            System.exit(-1);
        }
//        } else if(response.getStatus() != 200){
//            collectOthers(response.getStatus(), url, BASE_PATH);
//            logger.info("Thread 【 {} 】Proxy 【 {} 】 【 {} 】request【 {} 】is rejected by server! response ==> \n {}",
//                    Thread.currentThread().getName(),
//                    ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
//                    response.getStatus(),
//                    url,
//                    response.getBody());
////            System.exit(-1);
//        } else if (response.getStatus() == 200) {
//            collect200(url, BASE_PATH);
//        }
        logger.info("=================================Response=====================================");
    }

    private static String printResponse(HttpResponse<String> response, String url) {
        logger.info("               Response 详情：");
        logger.info("=================================Response=====================================");
        logger.info("Thread 【 {} 】Proxy 【 {} 】 Response [ statusText => {}, status = {} ]",
                Thread.currentThread().getName(),
                ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
                response.getStatusText(),
                response.getStatus());
        if (response.getStatus() == 403) {
            collect403(url, BASE_PATH);
            logger.info("Thread 【 {} 】Proxy 【 {} 】 【 {} 】request【 {} 】is rejected by server! response ==> \n {}",
                    Thread.currentThread().getName(),
                    ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
                    response.getStatus(),
                    url,
                    response.getBody());
            System.exit(-1);
//            throw new RuntimeException("代理异常");
//            return "403";
        } else if(response.getStatus() == 404){

            collectOthers(response.getStatus(), url, BASE_PATH );
            logger.info("Thread 【 {} 】Proxy 【 {} 】 【 {} 】request【 {} 】is rejected by server! response ==> \n {}",
                    Thread.currentThread().getName(),
                    ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
                    response.getStatus(),
                    url,
                    response.getBody());
//            return "others";
//            System.exit(-1);
//            throw new RuntimeException("代理异常");
        } else if (response.getStatus() == 200) {
            collect200(url, BASE_PATH);
        } else {
            collectOthers(response.getStatus(), url, BASE_PATH );
            logger.info("Thread 【 {} 】Proxy 【 {} 】 【 {} 】request【 {} 】is rejected by server! response ==> \n {}",
                    Thread.currentThread().getName(),
                    ProxyFetchUtil.getProxyInfo() != null ? ProxyFetchUtil.getProxyInfo()[0] : "",
                    response.getStatus(),
                    url,
                    response.getBody());
//            return "others";
//            System.exit(-1);
            throw new RuntimeException("代理异常");
        }
        logger.info("=================================Response=====================================");

        return response.getBody();
    }

    private static void printRequest(String url, Map requestParameters, Map<String, String> headers) {
        printUrl(url);
        if (requestParameters != null) {
            printRequestParameters(requestParameters);
        }
        printCookies();
        if(headers != null) {
            printHeaders(headers);
        }
    }

//        public static final String projectPath = "/Users/houjinxin/Documents/";
    public static final String projectPath = "D:/ws/";



    public static final String NEW_CAR_BASE_PATH = projectPath + "edmunds/res/NEWCAR";
    public static final String USED_CAR_BASE_PATH = projectPath + "edmunds/res/USEDCAR";
    public static String BASE_PATH = NEW_CAR_BASE_PATH;


    public static void appendStringToFile(String str, String filePath, String filename) {
        try {
            FileUtils.writeLines(new File(filePath + "/" + filename + ".txt"), new ArrayList<String>(){{
                add(str);
            }}, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void collect403(String url, String filePath){
        appendStringToFile(url, filePath,"403");
    }
    private static void collectOthers(int statusCode, String url, String filePath){
        appendStringToFile(statusCode + " " + url, filePath,"others");
    }
    private static void collect200(String url, String filePath){
        appendStringToFile(url, filePath, "200");
    }


}
